package net.sourceforge.jweb.jstl.easyui;

import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import net.sourceforge.jweb.annotation.Attribute;
import net.sourceforge.jweb.annotation.Tag;
import net.sourceforge.jweb.annotation.Taglib;
import net.sourceforge.jweb.json.JSONObject;
import net.sourceforge.jweb.jstl.JsSupport;
import net.sourceforge.jweb.jstl.helper.JstlUtil;
import net.sourceforge.jweb.util.FreeMarkerUtil;

/**
 * 封装datagrid, 主要目的是自动的计算column，帮定渲染器，编辑器
 */
@Taglib(shortName = "easyui", uri = "http://jweb.sourceforge.net/easyui")
@Tag(name = "datagrid", description = "datagrid tag")
public class DataGridTag extends BaseEasyuiTag implements JsSupport {
	private final static Logger LOGGER = LoggerFactory.getLogger(DataGridTag.class);
	private static final long serialVersionUID = 1L;
	/**
	 * column主要利用java反射，自动为bean解析编辑器
	 */
	Map<String, Map<String, Object>> columnConfigurations;
	Map<String, Map<String, Object>> columnFilters;
	List<RequestParameterTag> extralParams;
	List<String> otherCodeBlocks;
	List<String> customButtons;

	private String gridId;
	private String beanClass = "";

	private String queryUrl = "";
	private String addUrl = "";
	private String updateUrl = "";
	private String deleteUrl = "";

	private boolean enableAdd = true;
	private boolean enableEdit = true;
	private boolean enableDelete = true;
	private String editingField;

	private String method = "POST";
	private boolean multiSort = true;
	private boolean remoteFilter = true;
	private boolean clientPaging = false;
	private boolean pagination = true;
	private boolean border = false;
	private boolean showFooter = true;
	private boolean singleSelect = false;
	private boolean fit = true;
	private boolean fitColumns = true;
	private boolean enableFilter = true;
	private boolean enableAssociation = true;

	@Override
	public int doStartTag() throws JspException {
		LOGGER.debug("start rendering");
		columnConfigurations = new LinkedHashMap<String, Map<String, Object>>();
		columnFilters = new LinkedHashMap<String, Map<String, Object>>();
		extralParams = new ArrayList<>();
		otherCodeBlocks = new ArrayList<>();
		customButtons = new ArrayList<>();
		try {
			// 给对象生成默认的数据列
			Class<?> modelBeanClassObj = Class.forName(beanClass);
			BeanInfo modelBeanInfo = Introspector.getBeanInfo(modelBeanClassObj);
			PropertyDescriptor[] modelBeanProperties = modelBeanInfo.getPropertyDescriptors();
			JstlUtil.sortAsNaturalOrder(modelBeanProperties, this.getClass());
			int columnIndex = 0;
			for (PropertyDescriptor modelBeanPropertyDescriptor : modelBeanProperties) {
				Method readMethod = modelBeanPropertyDescriptor.getReadMethod();
				if (readMethod == null) {
					continue;
				}
				// just put attributes of this class's same package
				String classOfMethod = readMethod.getDeclaringClass().getName();
				if (!classOfMethod.startsWith(modelBeanClassObj.getPackage().getName())) {
					continue;
				}
				// field's properties
				Map<String, Object> columnDefiniation = new LinkedHashMap<>();
				String modelBeanFieldName = modelBeanPropertyDescriptor.getName();

				// put easyui column
				columnDefiniation.put("field", "'" + modelBeanFieldName + "'");

				if (!StringUtils.hasText(editingField)) {
					editingField = modelBeanFieldName;// 自动把第一个字段设置为可编辑的标识字段
				}

				// 生成默认的列配置
				columnDefiniation.put("title", "'" + modelBeanFieldName + "'");// default title is filed name
				// put align base one filed class type
				String fieldTypeName = modelBeanPropertyDescriptor.getPropertyType().getSimpleName();
				columnDefiniation.put("align", JstlUtil.getAlignment(fieldTypeName));
				columnDefiniation.put("width", JstlUtil.getDefaultWidth(fieldTypeName));
				// 自然的列序
				columnDefiniation.put("columnIndex", columnIndex++);
				// editor config according to filed type
				columnDefiniation.put("editor", JstlUtil.defaultDataGridEditor(fieldTypeName));

				columnConfigurations.put(modelBeanFieldName, columnDefiniation);

				// ***********************column filter*******************
				columnFilters.put(modelBeanFieldName,
						JstlUtil.defaultColumnFilter(modelBeanFieldName, fieldTypeName, gridId));
			}
			return EVAL_BODY_INCLUDE;
		} catch (Exception e) {
			e.printStackTrace();
		}

		return SKIP_BODY;
	}

	@Override
	public int doEndTag() throws JspException {
		try {
			Map<String, Object> freemarkerParams = new HashMap<>();
			// column configurations
			BeanInfo tagBinfo = Introspector.getBeanInfo(this.getClass());
			PropertyDescriptor[] tagProperties = tagBinfo.getPropertyDescriptors();
			JstlUtil.sortAsNaturalOrder(tagProperties, this.getClass());
			for (PropertyDescriptor descriptor : tagProperties) {
				Method readMethod = descriptor.getReadMethod();
				if (readMethod == null) {
					continue;
				}
				// just put attributes of this class's same package
				String classOfMethod = readMethod.getDeclaringClass().getName();
				if (!classOfMethod.startsWith(this.getClass().getPackage().getName())) {
					continue;
				}
				String name = descriptor.getName();
				Object value = JstlUtil.getNaturalValue(descriptor.getReadMethod(), this);
				if (value != null) {
					freemarkerParams.put(name, value);
				}
			}

			// then put other data grid properties
			if (this.entity != null) {
				mergeJsonObject(this.properties, (JSONObject) this.entity.getWrappedObject());
			}
			freemarkerParams.putAll(this.properties);

			// columns
			List<Map<String, Object>> sortedColumns = new ArrayList<>(columnConfigurations.values());
			Collections.sort(sortedColumns, new java.util.Comparator<Map<String, Object>>() {
				@Override
				public int compare(Map<String, Object> o1, Map<String, Object> o2) {
					Integer columnIndex1 = (Integer) o1.get("columnIndex");
					Integer columnIndex2 = (Integer) o2.get("columnIndex");
					return columnIndex1 - columnIndex2;
				}
			});

			// 去掉非easyui字段
			for (Map<String, Object> columnMap : sortedColumns) {
				columnMap.remove("columnIndex");
				columnMap.remove("overrideEditorType");
			}

			for (String columnName : new ArrayList<>(columnFilters.keySet())) {
				if (!columnConfigurations.containsKey(columnName)) {
					columnFilters.remove(columnName);
				}
			}

			freemarkerParams.put("sortedColumnDefination", sortedColumns);
			freemarkerParams.put("columns", JstlUtil.jsonify(sortedColumns));
			freemarkerParams.put("editingField", editingField);// 默认计算的编辑标识列名
			freemarkerParams.put("extralParamsBlock", evalExtralParamStatementBlock(this.extralParams));
			freemarkerParams.put("queryParamsBlock", evalQueryParamStatementBlock(this.extralParams));
			// filter
			freemarkerParams.put("columnFilters", JstlUtil.jsonify(this.columnFilters.values()));

			renderCustomButtons(freemarkerParams);

			JspWriter out = this.pageContext.getOut();
			out.write("<table id=\"" + gridId + "\"></table>");// just write html
			out.flush();

			String content = FreeMarkerUtil.processTemplate("datagrid.fmt", freemarkerParams);
			this.addLazyPageContent(content);// lazy write
		} catch (Exception e) {
			throw new JspException(e);
		}

		return super.doEndTag();
	}

	private void renderCustomButtons(Map<String, Object> freemarkerParams) {
		if (this.enableAdd) {
			this.customButtons.add(0, "{iconCls:'icon-add',handler:function(){appendRow();}}");
		}
		if (this.enableDelete) {
			this.customButtons.add(0, "{iconCls:'icon-remove',handler:function(){deleteRow();}}");
		}
		freemarkerParams.put("customButtonsJS", customButtons.toString());
	}

	private String evalExtralParamStatementBlock(List<RequestParameterTag> extralParams) throws Exception {
		StringBuilder builder = new StringBuilder(128);
		for (RequestParameterTag tag : extralParams) {
			builder.append("param.").append(tag.getFieldName()).append("=").append(tag.evalJqueryStatement())
					.append(";\n");
		}
		return builder.toString();
	}

	private String evalQueryParamStatementBlock(List<RequestParameterTag> extralParams) throws Exception {
		StringBuilder builder = new StringBuilder(128);
		for (RequestParameterTag tag : extralParams) {
			builder.append(tag.getFieldName()).append(":").append(tag.evalJqueryStatement()).append(",\n");
		}
		if (extralParams.size() > 0) {
			builder.delete(builder.length() - 2, builder.length());
		}
		return builder.toString();
	}

	@Attribute(required = true, description = "you java pojo class name")
	public String getBeanClass() {
		return beanClass;
	}

	public void setBeanClass(String beanClass) {
		this.beanClass = beanClass;
	}

	@Attribute(required = true, description = "backend query url")
	public String getQueryUrl() {
		return queryUrl;
	}

	public void setQueryUrl(String queryUrl) {
		this.queryUrl = queryUrl;
	}

	@Attribute(required = false, description = "backend add url")
	public String getAddUrl() {
		return addUrl;
	}

	public void setAddUrl(String addUrl) {
		this.addUrl = addUrl;
	}

	@Attribute(required = false, description = "backend update url")
	public String getUpdateUrl() {
		return updateUrl;
	}

	public void setUpdateUrl(String updateUrl) {
		this.updateUrl = updateUrl;
	}

	@Attribute(required = false, description = "backend delete url")
	public String getDeleteUrl() {
		return deleteUrl;
	}

	public void setDeleteUrl(String deleteUrl) {
		this.deleteUrl = deleteUrl;
	}

	@Attribute(required = false, description = "the editing indecator")
	public String getEditingField() {
		return editingField;
	}

	public void setEditingField(String editingField) {
		this.editingField = editingField;
	}

	@Attribute(required = false, description = "enable editing or not, default true")
	public boolean isEnableEdit() {
		return enableEdit;
	}

	public void setEnableEdit(boolean enableEdit) {
		this.enableEdit = enableEdit;
	}

	@Attribute(required = false, description = "enable add or not, default true")
	public boolean isEnableAdd() {
		return enableAdd;
	}

	public void setEnableAdd(boolean enableAdd) {
		this.enableAdd = enableAdd;
	}

	@Attribute(required = false, description = "enable delete or not, default true")
	public boolean isEnableDelete() {
		return enableDelete;
	}

	public void setEnableDelete(boolean enableDelete) {
		this.enableDelete = enableDelete;
	}

	@Attribute(required = true, description = "grid unique id")
	public String getGridId() {
		return gridId;
	}

	public void setGridId(String gridId) {
		this.gridId = gridId;
	}

	public String getMethod() {
		return method;
	}

	public void setMethod(String method) {
		this.method = method;
	}

	public boolean isMultiSort() {
		return multiSort;
	}

	public void setMultiSort(boolean multiSort) {
		this.multiSort = multiSort;
	}

	public boolean isRemoteFilter() {
		return remoteFilter;
	}

	public void setRemoteFilter(boolean remoteFilter) {
		this.remoteFilter = remoteFilter;
	}

	public boolean isPagination() {
		return pagination;
	}

	public void setPagination(boolean pagination) {
		this.pagination = pagination;
	}

	public boolean isBorder() {
		return border;
	}

	public void setBorder(boolean border) {
		this.border = border;
	}

	public boolean isShowFooter() {
		return showFooter;
	}

	public void setShowFooter(boolean showFooter) {
		this.showFooter = showFooter;
	}

	public boolean isSingleSelect() {
		return singleSelect;
	}

	public void setSingleSelect(boolean singleSelect) {
		this.singleSelect = singleSelect;
	}

	public boolean isFit() {
		return fit;
	}

	public void setFit(boolean fit) {
		this.fit = fit;
	}

	public boolean isFitColumns() {
		return fitColumns;
	}

	public void setFitColumns(boolean fitColumns) {
		this.fitColumns = fitColumns;
	}

	public boolean isEnableFilter() {
		return enableFilter;
	}

	public void setEnableFilter(boolean enableFilter) {
		this.enableFilter = enableFilter;
	}

	@Attribute(required = false, description = "enable master detail association, default true, if enabled, load event and check event will publish to bus")
	public boolean isEnableAssociation() {
		return enableAssociation;
	}

	public void setEnableAssociation(boolean enableAssociation) {
		this.enableAssociation = enableAssociation;
	}

	public List<String> getOtherCodeBlocks() {
		return otherCodeBlocks;
	}

	@Attribute(required = false, description = "True to do paging on the client side. default false")
	public boolean isClientPaging() {
		return clientPaging;
	}

	public void setClientPaging(boolean clientPaging) {
		this.clientPaging = clientPaging;
	}
}
